#!/usr/bin/env python
import random, argparse, Queue, time, gc, sys, traceback

from datetime import timedelta, datetime
from multiprocessing import Process, Event, JoinableQueue

import sql, lib

from tracklist import TrackList
from sublist import SubList
from subfile import SubFile
from track import Track
from xdh_logging import LogWriter, initLog
from settings import settings
from cache import cache
from MPD import MPD
from playfile import PlayFile
from podcast import Podcast
from podcast_track import PodcastTrack
from tags import get_tag
from lastfm import LastFM

log = initLog("playlist")

INITIAL = r"-/<({[|Initial\]})>\-"

class PlayedProcess(Process):
	Q = JoinableQueue()
	updating = Event()

	def __init__(self):
		super(PlayedProcess, self).__init__()

		self.start()


	def initialize(self, played_tracks):
		self.played_tracks = played_tracks
		self.playfile = PlayFile()
		self.playlist = set(self.playfile.playlist)
		self.current = set(MPD) 
		if self.played_tracks == None:
			self.played = self.playlist - self.current
		else:
			self.played = set([t['filename'] for t in played_tracks])

		if 'podcast_track' in cache and cache['podcast_track'] in self.played:
			Podcast.putPlayedQ(cache['podcast_track'])
			self.played.remove(cache['podcast_track'])

	def run(self):
		try:
			while True:
				played_tracks = self.Q.get()
				if played_tracks == lib.STOP:
					PlayedProcess.Q.task_done()
					log.debug("Got stop from {name} Queue.".format(name = PlayedProcess.__name__))
					break

				elif played_tracks == INITIAL:
					self.initialize(None)

				else:
					self.initialize(played_tracks)

				PlayedProcess.updating.set()

				self.process()

				PlayedProcess.updating.clear()

				self.Q.task_done()
		except Exception, e:
			log.error("{name}: {msg}".format(name = e.__class__.__name__, msg = e))
			log.error("".join(traceback.format_exception(*sys.exc_info())))


	def process(self):
		"""
		Updates the tracks that have been played (no longer are in
		mpd's playlist).
		"""

		self.playfile.remove(self.played)

		self.playlist -= self.played

		if self.played_tracks == None:
			log.info("{orig} originally in playlist, {remain} remain, {played} played.".format(
				orig = len(self.playlist),
				remain = len(self.current),
				played = len(self.played))
			)

			log.info("Removing {count} tracks from playlist.".format(count = len(self.played)))
        
			log.info("Updating played track{plural} with new played date{plural}.".format(plural = "s" if len(self.played) != 1 else ""))
		
			self.played = list(self.played)
        
			random.shuffle(self.played)
        
			self.played_tracks = []

			if not 'latest_date' in cache:
				cache['latest_date'] = datetime.today()

			current_date = cache['latest_date']

			scrobbles = []

			for track in self.played:
				current_date = current_date + timedelta(seconds = Track(track).length)
        
				self.played_tracks.append({"filename": track, "played": current_date})

				scrobbles.append((track, current_date))

			if MPD.current != None:
				cache["current_track"] = lib.safe_unicode(MPD.current)
			else:
				cache["current_track"] = None

			LastFM.scrobble_batch(scrobbles)

		last_date = datetime(1970, 1, 1)

		for entry in self.played_tracks:
			if entry['played'] > last_date:
				last_date = entry['played']

		cache['latest_date'] = last_date

		sql.process(sql.TrackPlayedData, "insert", self.played_tracks)

		sql.Q.join()

		log.info("Retaining {count} tracks from original playlist.".format(count = len(self.playlist)))


class PlayedUpdater(object):
	running = False

	canProcess = Event()

	def __init__(self, lastfm):
		self.lastfm = lastfm
		self.played_tracks = []
		if "current_track" not in cache:
			if MPD.current == None:
				cache["current_track"] = None
			else:
				cache["current_track"] = lib.safe_unicode(MPD.current)
		elif cache["current_track"] == u'None':
			cache["current_track"] = None

	@property
	def updating(self):
		return PlayedProcess.updating.is_set()


	def __enter__(self):
		if not PlayedUpdater.running:
			log.info("Entering Played Track Updater.")
			PlayedUpdater.running = True

			self.process = PlayedProcess()

		return self

	def initialize(self):
		PlayedProcess.Q.put(INITIAL)

	@property
	def processable(self):
		return PlayedUpdater.canProcess.is_set()

	@processable.setter
	def processable(self, yes):
		if yes:
			PlayedUpdater.canProcess.set()
		else:
			PlayedUpdater.canProcess.clear()

	def check(self):
		if cache["current_track"] != MPD.current:
			if cache["current_track"] != None:
				if sql.PodcastTrackData.exists(cache["current_track"]):
					current_track = PodcastTrack(cache["current_track"])
				else:
					current_track = Track(cache["current_track"])
				if MPD.current != None:
					if sql.PodcastTrackData.exists(lib.safe_unicode(MPD.current)):
						new_track = PodcastTrack(lib.safe_unicode(MPD.current))
					else:
						new_track = Track(lib.safe_unicode(MPD.current))
					log.info(u"Track \"{old_title} - {old_artist}\" finished, now playing \"{new_title} - {new_artist}\".".format(old_title = current_track.title, old_artist = current_track.artist, new_title = new_track.title, new_artist = new_track.artist))
				else:
					log.info(u"Track \"{old_title} - {old_artist}\" finished. Playback has stopped.".format(old_title = current_track.title, old_artist = current_track.artist))

				if ('podcast_track' in cache and cache['podcast_track'] == cache["current_track"]) or sql.PodcastTrackData.exists(cache["current_track"]):
					Podcast.putPlayedQ(cache["current_track"])

				else:
					self.played_tracks.append({"filename": cache["current_track"], "played": datetime.today()})
			elif MPD.current != None:
				if MPD.current != None:
					if sql.PodcastTrackData.exists(cache["current_track"]):
						new_track = PodcastTrack(MPD.current)
					else:
						new_track = Track(MPD.current)
				log.info(u"Playing \"{new_title} - {new_artist}\".".format(new_title = new_track.title, new_artist = new_track.artist))
			else:
				log.info("Playback stopped.")

			if MPD.current == None:
				cache["current_track"] = None
			else:
				cache["current_track"] = lib.safe_unicode(MPD.current)
				self.lastfm.now_playing(lib.safe_unicode(MPD.current))

		if not self.updating and self.processable and len(self.played_tracks) > 0:
			PlayedProcess.Q.put(self.played_tracks)
			self.played_tracks = []


	def __exit__(self, type, value, tback):
		if PlayedUpdater.running:
			log.info("Exiting Played Track Updater.")

			if self.process.is_alive():
				log.debug("Sending stop to {name} Queue.".format(name = PlayedProcess.__name__))
				PlayedProcess.Q.put(lib.STOP)

			log.info('Joining Played Track Queue.')
			PlayedProcess.Q.join()

			log.info('Closing Played Track Queue.')
			PlayedProcess.Q.close()

			log.info("Joining Played Track Updater Process.")
			self.process.join()

			if type == None:
				log.error("{name}: {msg}".format(name = value.__class__.__name__, msg = value))
				log.error("".join(traceback.format_exception(*sys.exc_info())))


			log.info("Marking PlayedUpdater as no longer running.")
			PlayedUpdater.running = False


